package com.fun.fun.httpclient;


import com.fun.frame.SourceCode;
import com.fun.httpclient.ClientManage;
import com.fun.httpclient.RequestInfo;
import com.fun.mysql.MySqlTest;
import com.fun.profile.HttpClientConstant;
import com.fun.profile.SysInit;
import com.fun.utils.DecodeEncode;
import com.fun.utils.Time;
import com.fun.utils.message.AlertOver;
import net.sf.json.JSONObject;
import org.apache.http.*;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;

import java.io.*;
import java.util.*;

/**
 * 请求相关类，采用统一的静态方法，在登录后台管理页面是自动化设置cookie，其他公参由各自的base类实现header
 */
public class FanLibrary extends SourceCode {

    /**
     * 请求超时控制器
     */
    static RequestConfig requestConfig = getRequestConfig();

    /**
     * 自动设置cookies，如果有新的setcookies的header，则重新设置cookies
     * <p>
     * string类型的cookie值，老后台专用，其他统一传header
     * </p>
     */
    static String cookies;

    /**
     * 标记cookies是否被设置过，用于是否更新cookies
     */
    static boolean cookieSet = false;

    /**
     * 打印请求头和响应头，一次有效，在请求之前使用该方法
     */
    public static void setHeaderKey() {
        HEADER_KEY = true;
    }

    /**
     * 最近发送的请求
     */
    public static List<HttpRequestBase> requests = new ArrayList<>();

    /**
     * 是否显示请求所有header的开关
     */
    static boolean HEADER_KEY = false;

    /**
     * 方法已重载，获取get对象
     * <p>方法重载，主要区别参数，会自动进行urlencode操作</p>
     *
     * @param url  表示请求地址
     * @param args 表示传入数据
     * @return 返回get对象
     */

    public static HttpGet getHttpGet(String url, JSONObject args) {
        if (args == null || args.size() == 0)
            return getHttpGet(url);
        String uri = url + changeJsonToArguments(args);
        return getHttpGet(uri.replace(" ", ""));
    }

    /**
     * 方法已重载，获取get对象
     * <p>方法重载，主要区别参数，会自动进行urlencode操作</p>
     *
     * @param url 表示请求地址
     * @return 返回get对象
     */
    public static HttpGet getHttpGet(String url) {
        return new HttpGet(url);
    }

    /**
     * 获取post对象，以form表单提交数据
     * <p>方法重载，文字信息form表单提交，文件信息二进制流提交，具体参照文件上传的方法主食，post请求可以不需要参数，暂时不支持其他参数类型，如果是公参需要在url里面展示，需要传一个json对象，一般默认args为get公参，params为post请求参数</p>
     *
     * @param url    请求地址
     * @param params 请求数据，form表单形式设置请求实体
     * @return 返回post对象
     */
    public static HttpPost getHttpPost(String url, JSONObject params) {
        HttpPost httpPost = getHttpPost(url);
        setFormHttpEntity(httpPost, params);
        return httpPost;
    }

    /**
     * 获取httppost对象，没有参数设置
     * <p>方法重载，文字信息form表单提交，文件信息二进制流提交，具体参照文件上传的方法主食，post请求可以不需要参数，暂时不支持其他参数类型，如果是公参需要在url里面展示，需要传一个json对象，一般默认args为get公参，params为post请求参数</p>
     *
     * @param url
     * @return
     */
    public static HttpPost getHttpPost(String url) {
        return new HttpPost(url);
    }

    /**
     * 获取httppost对象，json格式对象，传参时手动tostring
     * <p>新重载方法，适应post请求json传参，估计utf-8编码格式</p>
     *
     * @param url
     * @param params
     * @return
     */
    public static HttpPost getHttpPost(String url, String params) {
        HttpPost httpPost = getHttpPost(url);
        httpPost.setEntity(new StringEntity(params, DEFAULT_CHARSET.toString()));
        return httpPost;
    }

    /**
     * * 获取httppost对象，json格式对象，传参时手动tostring
     * <p>新重载方法，适应post请求json传参</p>
     *
     * @param url
     * @param args
     * @return
     */
    public static HttpPost getHttpPost(String url, JSONObject args, String params) {
        return getHttpPost(url + changeJsonToArguments(args), params);
    }

    /**
     * 获取 httppost 请求对象
     * <p>方法重载，文字信息form表单提交，文件信息二进制流提交，具体参照文件上传的方法主食，post请求可以不需要参数，暂时不支持其他参数类型，如果是公参需要在url里面展示，需要传一个json对象，一般默认args为get公参，params为post请求参数</p>
     *
     * @param url    请求地址
     * @param args   请求地址参数
     * @param params 请求参数
     * @return
     */
    public static HttpPost getHttpPost(String url, JSONObject args, JSONObject params) {
        return getHttpPost(url + changeJsonToArguments(args), params);
    }


    /**
     * 获取 httpPost 对象
     * <p>方法重载，文字信息form表单提交，文件信息二进制流提交，具体参照文件上传的方法主食，post请求可以不需要参数，暂时不支持其他参数类型，如果是公参需要在url里面展示，需要传一个json对象，一般默认args为get公参，params为post请求参数</p>
     *
     * @param url    请求地址
     * @param args   请求通用参数
     * @param params 请求参数，其中二进制流必须是 file
     * @param file   文件
     * @return
     */
    public static HttpPost getHttpPost(String url, JSONObject args, JSONObject params, File file) {
        return getHttpPost(url + changeJsonToArguments(args), params, file);
    }

    /**
     * 获取 httpPost 对象
     * <p>方法重载，文字信息form表单提交，文件信息二进制流提交，具体参照文件上传的方法主食，post请求可以不需要参数，暂时不支持其他参数类型，如果是公参需要在url里面展示，需要传一个json对象，一般默认args为get公参，params为post请求参数</p>
     *
     * @param url    请求地址
     * @param params 请求参数，其中二进制流必须是 file
     * @param file   文件
     * @return
     */
    public static HttpPost getHttpPost(String url, JSONObject params, File file) {
        HttpPost httpPost = getHttpPost(url);
        setMultipartEntityEntity(httpPost, params, file);
        return httpPost;
    }

    /**
     * 设置二进制流实体，params 里面参数值为 file
     *
     * @param httpPost httpPsot 请求
     * @param params   请求参数
     * @param file     文件
     */
    private static void setMultipartEntityEntity(HttpPost httpPost, JSONObject params, File file) {
        String fileName = file.getName();
        InputStream inputStream = null;
        try {
            inputStream = new FileInputStream(file);
        } catch (FileNotFoundException e) {
            output(e);
        }
        Iterator<String> keys = params.keys();// 遍历 params 参数和值
        MultipartEntityBuilder builder = MultipartEntityBuilder.create();// 新建MultipartEntityBuilder对象
        while (keys.hasNext()) {
            String key = keys.next();
            String value = params.getString(key);
            if (value.equals("file")) {
                builder.addBinaryBody(key, inputStream, ContentType.create(HttpClientConstant.CONTENTTYPE_MULTIPART_FORM), fileName);// 设置流参数
            } else {
                StringBody body = new StringBody(value, ContentType.create(HttpClientConstant.CONTENTTYPE_TEXT, DEFAULT_CHARSET));// 设置普通参数
                builder.addPart(key, body);
            }
        }
        HttpEntity entity = builder.build();
        httpPost.setEntity(entity);
    }

    /**
     * 获取请求超时控制器
     *
     * @return
     */
    private static RequestConfig getRequestConfig() {
        return RequestConfig.custom().setConnectionRequestTimeout(HttpClientConstant.CONNECT_REQUEST_TIMEOUT).setConnectTimeout(HttpClientConstant.CONNECT_TIMEOUT).setSocketTimeout(HttpClientConstant.SOCKET_TIMEOUT).setCookieSpec(CookieSpecs.STANDARD).build();
    }


    /**
     * 发送请求之前，配置请求管理器，设置IP，user_agent和cookie
     *
     * @param request
     */
    protected static void beforeRequest(HttpRequestBase request) {
        request.setConfig(requestConfig);//设置请求配置
        HttpClientConstant.COMMON_HEADER.forEach(header -> request.addHeader(header));
        if (cookies != null && !Arrays.toString(request.getAllHeaders()).contains(REQUEST_HEADER_COOKIE))
            request.addHeader(REQUEST_HEADER_COOKIE, cookies);// 添加cookie
    }

    /**
     * 响应结束之后，处理响应头信息，如set-cookien内容
     *
     * @param response
     */
    private static void afterResponse(CloseableHttpResponse response) {
        List<Header> headers = Arrays.asList(response.getHeaders("Set-Cookie"));
        if (!headers.equals(new ArrayList<>())) setCookies(headers);
        if (cookieSet == false && cookies != null)
            cookieSet = true;
    }

    /**
     * 根据解析好的content，转化json对象
     *
     * @param content
     * @return
     */
    private static JSONObject getJsonResponse(String content) {
        JSONObject jsonObject = new JSONObject();
        try {
            jsonObject = JSONObject.fromObject(content);
        } catch (Exception e) {
            jsonObject.put("content", content);
            jsonObject.put("code", TEST_ERROR_CODE);
        }
        return jsonObject;
    }

    /**
     * 获取并检查code
     *
     * @param jsonObject
     * @return
     */
    private static int checkCode(JSONObject jsonObject, RequestInfo requestInfo) {
        int code = TEST_ERROR_CODE;
        try {
            code = jsonObject.getInt("code");
            if (ERROR_CODE_LIST.contains(code))
                new AlertOver("responseCode错误", jsonObject.toString(), requestInfo.getUrl(), requestInfo).sendSystemMessage();
        } catch (Exception e) {
            output("响应非标准响应体！", e);
        }
        return code;
    }

    /**
     * 根据响应获取响应实体
     *
     * @param response
     * @return
     */
    private static String getContent(CloseableHttpResponse response) {
        HttpEntity entity = response.getEntity();// 获取响应实体
        String content = EMPTY;
        try {
            content = EntityUtils.toString(entity, DEFAULT_CHARSET);// 用string接收响应实体
            EntityUtils.consume(entity);// 消耗响应实体，并关闭相关资源占用
        } catch (ParseException e1) {
            output("解析响应实体异常！", e1);
        } catch (IOException e1) {
            output("解析响应实体时java IO 异常！", e1);
        }
        return content;
    }

    /**
     * 获取响应实体
     *
     * @param request 请求对象
     * @return 返回json类型的对象
     */
    public static JSONObject getHttpResponse(HttpRequestBase request) {
        beforeRequest(request);
        JSONObject jsonObject = new JSONObject();
        RequestInfo requestInfo = new RequestInfo(request);
        if (HEADER_KEY) outputAllHeader(request);
        long start = Time.getTimeStamp();
        try (CloseableHttpResponse response = ClientManage.httpsClient.execute(request)) {
            long end = Time.getTimeStamp();
            if (HEADER_KEY) outputAllHeader(response);
            double elapsed_time = (double) (end - start) / 1000;
            if (response == null) return jsonObject;
            afterResponse(response);
            int status = response.getStatusLine().getStatusCode();
            String content = getContent(response);
            long data_size = content.length();
            jsonObject = getJsonResponse(content);
            int code = checkCode(jsonObject, requestInfo);
            if (status != HttpStatus.SC_OK)
                new AlertOver("响应状态码错误", "状态码错误：" + status, requestInfo.getUrl(), requestInfo).sendSystemMessage();
            if (response != null) response.close();
            MySqlTest.saveApiTestDate(requestInfo, data_size, elapsed_time, status, getMark(), code, LOCAL_IP, COMPUTER_USER_NAME);
        } catch (Exception e) {
            output(e);
            if (!SysInit.isBlack(requestInfo.getHost()))
                new AlertOver("接口请求失败", requestInfo.toString(), requestInfo.getUrl(), requestInfo).sendSystemMessage();
        } finally {
            if (!SysInit.isBlack(requestInfo.getHost())) {
                if (requests.size() > 9) requests.remove(0);
                requests.add(request);
            }
        }
        return jsonObject;
    }

    /**
     * 设置post接口上传表单，默认的编码格式
     *
     * @param httpPost post请求
     * @param params   参数
     */
    private static void setFormHttpEntity(HttpPost httpPost, JSONObject params) {
        Iterator<String> keys = params.keys();
        List<NameValuePair> formparams = new ArrayList<NameValuePair>();
        while (keys.hasNext()) {
            String key = keys.next();
            String value = params.getString(key);
            formparams.add(new BasicNameValuePair(key, value));
        }
        UrlEncodedFormEntity entity = null;
        entity = new UrlEncodedFormEntity(formparams, DEFAULT_CHARSET);
        httpPost.setEntity(entity);
    }

    /**
     * 解析response，使用char数组，注意编码格式
     * <p>自定义解析响应实体的方法，暂不采用</p>
     *
     * @param response 传入的response，非closedresponse
     * @param encoding 编码格式
     * @return string类型的response
     */
    private static String parseResponeEntityByChar(HttpResponse response, String encoding) {
        StringBuffer buffer = new StringBuffer();// 创建并实例化stringbuffer，存放响应信息
        InputStream inputStream = null;
        InputStreamReader reader = null;
        try {
            HttpEntity entity = response.getEntity();// 获取响应实体
            inputStream = entity.getContent();// 创建并实例化字节输入流，使用响应实体作为输入流
            reader = new InputStreamReader(inputStream, encoding);// 创建并实例化字符输入流，并设置编码格式
            char[] buff = new char[512];// 创建并实例化字符数组
            int length = 0;// 声明变量length，表示读取长度
            while ((length = reader.read(buff)) != -1) {// 循环读取字符输入流
                String x = new String(buff, 0, length);// 获取读取到的有效内容
                buffer.append(x);// 将读取到的内容添加到stringbuffer中
            }
            reader.close();
            inputStream.close();
        } catch (Exception e) {
            output("解析响应实体失败！", e);
        }
        return buffer.toString();
    }

    /**
     * 把json数据转化为参数，为get请求和post请求stringentity的时候使用
     *
     * @param argument 请求参数，json数据类型，map类型，可转化
     * @return 返回拼接参数后的地址
     */
    public static String changeJsonToArguments(JSONObject argument) {
        if (argument == null || argument.isEmpty()) return EMPTY;
        Set<String> keys = argument.keySet();
        for (String key : keys) {
            String value = argument.getString(key);
            argument.put(key, DecodeEncode.urlEncoderText(value));
        }
        String one = argument.toString();
        String two = "?" + one.substring(1, one.length() - 1).replace(",", "&").replace(":", "=").replace("\"", "");
        return two;
    }

    /**
     * 根据接口返回信息设置cookies
     *
     * @param headers
     */
    private static void setCookies(List<Header> headers) {
        int headerSize = headers.size();
        for (int i = 0; i < headerSize; i++) {// 遍历header
            if (cookieSet) {
                cookies = null;
                cookieSet = false;
            }
            String value = headers.get(i).getValue().split(";")[0];
            if (cookies == null) {// 拼接cookie
                cookies = value;
            } else {
                cookies = cookies + ";" + value;
            }
        }
    }

    /**
     * 生成header
     *
     * @param name
     * @param value
     * @return
     */
    public static Header getHeader(String name, String value) {
        return new BasicHeader(name, value);
    }

    /**
     * 是否是正常返回json格式，校验code=0，以及最外层key包含datainfo
     *
     * @param response 返回json
     * @return
     */
    public static boolean isRightResponse(JSONObject response) {
        try {
            if (response.getInt("code") == 0 && response.containsKey(DATAINFO)) return true;
        } catch (Exception e) {
            output("验证响应错误!", e);
        }
        return false;
    }

    /**
     * 输出所有的header
     *
     * @param request
     * @return
     */
    public static JSONObject outputAllHeader(HttpRequestBase request) {
        Header[] allHeaders = request.getAllHeaders();
        JSONObject jsonObject = changeArraysToJson(allHeaders, ": ");
        output(jsonObject);
        return jsonObject;
    }

    /**
     * 输出响应的header
     *
     * @param response
     * @return
     */
    public static JSONObject outputAllHeader(CloseableHttpResponse response) {
        Header[] allHeaders = response.getAllHeaders();
        JSONObject jsonObject = changeArraysToJson(allHeaders, ": ");
        output(jsonObject);
        HEADER_KEY = false;
        return jsonObject;
    }


    /**
     * 结束测试，关闭连接池
     */
    public static void testOver() {
        MySqlTest.mySqlQueryOver();
        try {
            ClientManage.httpsClient.close();
        } catch (IOException e) {
            output(e);
        }
    }

}